﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace M3G {
    public enum PrimitiveType {
        Triangles = 8,
        Lines = 9,
        PointSprites = 10
    }

    public static class PrimitiveTypeExtensions {
        public static int ToVertexCount (this PrimitiveType type) {
            switch (type) {
                case PrimitiveType.Triangles: return 3;
                case PrimitiveType.Lines: return 2;
                case PrimitiveType.PointSprites: return 1;
                default: return 0;
            }
        }
    }


    public class IndexBuffer : Object3D {
        #region Constant
        #endregion

        #region Field
        PrimitiveType     type;
        int     vertexCount;
        bool    readable;
        int[]   indices;
        #endregion

        #region Method

        // リスト形式
        public IndexBuffer (PrimitiveType type, int primitiveCount, int firstIndex) {
            if (type < PrimitiveType.Triangles || type > PrimitiveType.PointSprites) {
                throw new ArgumentException ("Type must be Triangles,Lines,PointSprites, type=" + type);
            }
            if (primitiveCount < 0) {
                throw new ArgumentOutOfRangeException ("Primitive count must be positive number or 0, count=" + primitiveCount);
            }
            var indexCount = primitiveCount * type.ToVertexCount ();
            if (firstIndex < 0 || firstIndex + indexCount > 65535) {
                throw new ArgumentOutOfRangeException ("Index must be in [0,65535], first=" + firstIndex + ",last=" + firstIndex + indexCount);
            }

            this.type = type;
            this.readable = true;
            this.vertexCount = type.ToVertexCount ();

            this.indices = new int[primitiveCount * vertexCount];
            for (var i=0; i < primitiveCount * vertexCount; i++) {
                this.indices[i] = firstIndex + i;
            }
        }

        // リスト形式
        public IndexBuffer (PrimitiveType type, int primitiveCount, int[] indices) {
            if (indices == null) {
                throw new ArgumentNullException ("Indices is null");
            }
            if (type < PrimitiveType.Triangles || type > PrimitiveType.PointSprites) {
                throw new ArgumentException ("Type must be Triangles,Lines,PointSprites, type=" + type);
            }
            if (primitiveCount < 0) {
                throw new ArgumentOutOfRangeException ("Primitive count must be positive number or 0, count=" + primitiveCount);
            }
            int indexCount = primitiveCount * type.ToVertexCount ();
            if (indices.Length < indexCount) {
                throw new ArgumentOutOfRangeException ("Length of indices are less than required, len=" + indices.Length + "<" + indexCount);
            }
            foreach (var index in indices) {
                if (index < 0 || index > 65535) {
                    throw new ArgumentOutOfRangeException ("Index must be in [0,65535], index=" + index);
                }
            }

            this.type = type;
            this.readable = true;
            this.vertexCount = type.ToVertexCount ();
            this.indices = new int[primitiveCount * vertexCount];

            Array.Copy (indices, 0, this.indices, 0, primitiveCount * vertexCount);
        }

        // ストリップ形式
        public IndexBuffer (PrimitiveType type, int[] stripLengths, int firstIndex) {
            if (stripLengths == null) {
                throw new ArgumentNullException ("StripLengths is null");
            }
            if (type != PrimitiveType.Triangles && type != PrimitiveType.Lines) {
                throw new ArgumentException ("Type must be Triangle,Lines, type=" + type);
            }
            if (stripLengths.Length == 0) {
                throw new ArgumentException ("StripLengths is empty");
            }
            if (stripLengths.Any (x => x < type.ToVertexCount ())) {
                throw new ArgumentException ("Length of strip is less than required");
            }
            var vertexCount = type.ToVertexCount ();
            var indexCount = stripLengths.Sum (x => (x - (vertexCount - 1)) * vertexCount);
            if (firstIndex < 0 || firstIndex + indexCount > 65535) {
                throw new ArgumentException ("Index must be in [0,65535], first=" + firstIndex + ",last=" + firstIndex + indexCount);
            }

            this.type = type;
            this.readable = true;
            this.vertexCount = type.ToVertexCount ();
            this.indices = new int[indexCount];

            var i = 0;
            var k = firstIndex;
            foreach (var stripLength in stripLengths) {
                var primitiveCount = stripLength - (vertexCount - 1);
                for (var p=0; p < primitiveCount; p++) {
                    for (var v=0; v < vertexCount; v++) {
                        this.indices[i++] = k + v;
                    }
                    k += 1;
                }
                k += vertexCount - 1;
            }

        }

        // ストリップ形式
        public IndexBuffer (PrimitiveType type, int[] stripLengths, int[] indices) {
            if (stripLengths == null) {
                throw new ArgumentNullException ("StripLengths is null");
            }
            if (indices == null) {
                throw new ArgumentNullException ("Indices is null");
            }
            if (type != PrimitiveType.Triangles && type != PrimitiveType.Lines) {
                throw new ArgumentException ("Type must be Triangle,Lines, type=" + type);
            }
            if (stripLengths.Length == 0) {
                throw new ArgumentException ("StripLengths is empty");
            }
            if (stripLengths.Any (x => x < type.ToVertexCount ())) {
                throw new ArgumentException ("Length of strip is less than required");
            }
            var required = stripLengths.Sum ();
            if (indices.Length < required) {
                throw new ArgumentException ("Length of indices is less than required, len=" + indices.Length + "<" + required);
            }

            this.type = type;
            this.readable = true;
            this.vertexCount = type.ToVertexCount ();

            var indexCount = stripLengths.Sum (x => (x - (vertexCount - 1)) * vertexCount);
            this.indices = new int[indexCount];

            int i = 0;
            int k = 0;
            foreach (var stripLength in stripLengths) {
                var primitiveCount = stripLength - (vertexCount - 1);
                for (var p=0; p < primitiveCount; p++) {
                    for (var v=0; v < vertexCount; v++) {
                        this.indices[i++] = indices[k + v];
                    }
                    k += 1;
                }
                k += vertexCount - 1;
            }
        }

        /**
         * コピーコンストラクター
         * */
        protected IndexBuffer (IndexBuffer ibuf)
            : base (ibuf) {
            this.type = ibuf.type;
            this.vertexCount = ibuf.vertexCount;
            this.readable = ibuf.readable;
            this.indices = (int[])ibuf.indices.Clone ();
        }


        public void Commit () {
            this.readable = false;
        }

        public int IndexCount {
            get { return indices.Length; }
        }

        public int[] GetIndices () {
            return (int[])indices.Clone ();
        }

        public PrimitiveType PrimitiveType {
            get { return type; }
        }

        public bool Readable {
            get { return readable; }
        }

        public override Object3D Duplicate () {
            return new IndexBuffer (this);
        }

        internal byte[] ByteArray () {
            var byteArray = new byte[IndexCount * sizeof (int)];
            for (var i=0; i < IndexCount; i++) {
                BitConverter.GetBytes (indices[i]).CopyTo (byteArray, i * sizeof (int));
            }
            return byteArray;
        }

        #endregion

    }
}
